
#global state here sees if a Tk root instance needs to be created
g_hasTkRoot = False

import rcontrolutil
import rcontrol
import rwindowutil
import Tkinter


import rmenu
from rbutton import RButton
from rcheckbox import RCheckbox
from rfield import RField
from rframe import RFrame
from rlistbox import RListbox
from rinput import RInput
from rlabel import RLabel
from roptionmenu import ROptionMenu
from rslider import RSlider



'''
w.begin_frame()
w.button('hello', onhello)
w.end_frame()

same as

with w.add_frame():
	w.button('hello', onhello)

same as

fr = RFrame(parent=frparent).add()
btn = RButton('hello', onhello, parent=fr).add()
'''


class RWindow(rcontrol.RControl):
	"""
	Properties:
	*bgcolor* - Background color of the window. RGB triplet, such as (255,0,0) for pure red.
	*borderwidth* - Integer, the width of the border.
	*cursor* - String with cursor name. See http://infohost.nmt.edu/tcc/help/pubs/tkinter/cursors.html.
	*height* - Height, in pixels. Note that if the pack geometry is used, setting this may not take effect.
	*width* - Width, in pixels. Note that if the pack geometry is used, setting this may not take effect.
	*borderstyle* - Border style. One of 'sunken','raised','groove','ridge','flat'. Default is 'raised'. Set to 'sunken' and it will appear pressed.
	
	*transparency* - Transparency of window from 0.0 to 1.0. Might not be supported in all operating systems.
	*enabled* - If false, disables entire window. Might not be supported in all operating systems.
	*topmost* - Window appears above all windows. Might not be supported in all operating systems.
	*title* - Text to appear in title.
	"""
	
	_basecontrol = None
	
	_startMainloop = False
	
	
	# For packing
	_container_stack = None
	_debug_frames = False
	_utilClass = None #created when needed
	_tkmenubar = None
	_tkmenuprev = None
	
	def __init__(self, top=None, **options):
		global g_hasTkRoot
		
		if top==None:
			if g_hasTkRoot: top=False
			else: top=True
		if top==True:
			#create a TkRoot, and start a mainloop when ready to start().
			g_hasTkRoot = True
			self._startMainloop = True
			self._basecontrol = Tkinter.Tk()
		elif top==False:
			#just create a top level.
			self._startMainloop = False
			self._basecontrol = Tkinter.Toplevel()
			
		
		# To simplify packing logic, create an initial frame that contains everything else.
		self._topframe = RFrame(parent=self._basecontrol, orient=rcontrolutil.V)
		#have to add to form manually, so not caught in a loop.
		self._topframe._basecontrol.pack(expand=True, fill=Tkinter.BOTH)
		
		self._topframe.add(fill=True, resizable=True)
		self._container_stack = [self._topframe]
		
		self.set_many(**options)
		
		
	def start(self):
		if rcontrol.strictMode and len(self._container_stack)!=1: 
			raise rcontrolutil.RException('Warning: you appear to be missing a w.end_frame() call. %d frames unclosed.'%(len(self._container_stack)-1))
		
		#client code *must* call this to start main loop.
		if self._startMainloop: self._basecontrol.mainloop()
	
	
	
	def _createAndAddControl(self, controlclass, controllistargs, args):
		controlargs = {}
		layoutargs = {}
		for key in args:
			if key in rcontrol.RControl._layoutArguments: layoutargs[key] = args[key]
			else:	controlargs[key] = args[key]
		
		parent = self._container_stack[-1] #always a RFrame().
		
		#check no conflicting layout styles (simply a guideline to help, could be worked around (via place) although that'd break elsewhere)
		layoutmethod = self._determineLayoutMethod(args)
		if (layoutmethod=='pack' and parent._children_layoutmethod=='grid') or (layoutmethod=='grid' and parent._children_layoutmethod=='pack'):
			raise rcontrolutil.RException('Parent cannot have both pack and grid')
		parent._children_layoutmethod = layoutmethod
		
		controlargs['parent']=parent
		#create and add the widget
		obj = controlclass(*controllistargs, **controlargs)
		obj.add(**layoutargs)
		
		#for pack layout, take a note of parent's H or V orientation
		obj._pack_orientation = parent._pack_children_orientation
		
		return obj

	#todo: make _debug_frames work better
	def debug_frames(self):
		self._debug_frames = True
		
	def add_frame(self, **kwargs): #MUST be used inside with statement
		kwargs['_exitWithCallback']=self._frameCloseCallback #enforce use of with statement
		return self.begin_frame( **kwargs)
		
	def begin_frame(self, **kwargs):
		frame = self._createAndAddControl(RFrame,[], kwargs)
		self._container_stack.append(frame) #push onto the stack
		if self._debug_frames: 
			import random
			frame.bgcolor = (random.randint(45,255),random.randint(45,255),random.randint(45,255))
		return frame
	
	def end_frame(self):
		"""Close the current container. called by user *not* implicitly from with statement"""
		if len(self._container_stack) <= 1:
			raise rcontrolutil.RException("More end_frame()s than begin_frame()s.")
		else:
			prevFrame = self._container_stack.pop()
			if prevFrame._exitWithCallback!=None:
				# prevent something like with w.add_frame(): w.end_frame()
				raise rcontrolutil.RException("Frame created by add_frame() must be used in a 'with' statement and cannot be closed by end_frame().")
			
	def _frameCloseCallback(self, frame): #called at end of with: statement
		'''we must prevent case like with w.frame().add(): w.button(); w.endframe() #ends wrong frame'''
		if len(self._container_stack) <= 1 or self._container_stack[-1] != frame:
			raise rcontrolutil.RException("Misaligned begin_frame(). Make sure all begin_frame()s have exactly one corresponding end_frame().")
		
		self._container_stack.pop()
	
	
	def add_button(self, strLabel='',fncallback=None, **kwargs):
		"""create Button control."""
		return self._createAndAddControl(RButton, [strLabel, fncallback], kwargs)
	
	def add_image(self, **kwargs):
		"""Create Image control."""
		img = self._createAndAddControl(RImage, [], kwargs)
		# do not worry about storing a reference here, 
		return img
	
	def add_field(self, strDefaultText='',  **kwargs):
		"""Create Field control."""
		return self._createAndAddControl(RField, [strDefaultText], kwargs)
		
	def add_label(self, strText='',  **kwargs):
		"""Create Label control."""
		return self._createAndAddControl(RLabel, [strText], kwargs)
		
	def add_input(self, strText='',  **kwargs):
		"""Create Input control."""
		return self._createAndAddControl(RInput, [strText], kwargs)
		
	def add_listbox(self,  **kwargs):
		"""Create Listbox control."""
		return self._createAndAddControl(RListbox, [], kwargs)
	
	def add_optionmenu(self, listDisplays=None, **kwargs):
		"""Create OptionMenu control."""
		return self._createAndAddControl(ROptionMenu, [listDisplays], kwargs)
		
	def add_checkbox(self, strLabel=None, **kwargs):
		"""Create Checkbox control."""
		return self._createAndAddControl(RCheckbox, [strLabel], kwargs)
	def add_slider(self, range=(0,100), bVertical=True, **kwargs):
		"""Create Slider control."""
		return self._createAndAddControl(RSlider, [range, bVertical], kwargs)
	
	
	#todo: consider shortcuts. event shortcut hotkeys for add_menu
	def add_menu(self, strText):
		if self._tkmenubar==None:
			self._tkmenubar =Tkinter.Menu(self._basecontrol)
			self._basecontrol.configure(menu = self._tkmenubar)
		
		label, underline = rcontrolutil.stringToUnderlinePosition(strText)
		self._tkmenuprev = Tkinter.Menu(self._tkmenubar, tearoff=0)
		self._tkmenubar.add_cascade(label=label, menu=self._tkmenuprev, underline=underline)
	def add_item(self, strText, fnCommand):
		return self.add_items(*(strText, fnCommand))
	def add_items(self, *args):
		return rmenu.add_items(self._tkmenuprev, args)
	def add_check_item(self, strText, fnCommand=None, checked=True):
		return rmenu.add_check_item(self._tkmenuprev, strText, fnCommand=fnCommand, bChecked=checked)
		
	
	
	##Other methods
	def set_aspect(self, min, max):
		"""Constrain the ratio between width and height. The ratio will be held between min and max. Apparently doesn't work in Windows."""
		c = 1024.
		self._basecontrol.aspect(int(min*c),int(c),int(max*c),int(c))
	
	def set_style(self, strStyle):
		"""Set the style of the window. One of 'normal','tool','bare'. This can only be set once. """
		if strStyle=='normal':
			elf._basecontrol.attributes('-toolwindow',0)
		elif strStyle=='tool':
			#~ self._basecontrol.transient()
			self._basecontrol.attributes('-toolwindow',1)
		elif strStyle=='bare':
			self._basecontrol.overrideredirect(1)
	
	def set_rect(self, x,y,xwidth,yheight):
		"""Set rectangle of the window. Uses screen coordinates."""
		s = "%dx%d%+d%+d" % (xwidth, yheight, x, y)
		self._basecontrol.geometry(s)
	def get_rect(self):
		"""Get rectangle of the window. Uses screen coordinates."""
		s = self._basecontrol.geometry()
		# This code from http://effbot.org/tkinterbook/wm.htm
		import re
		m = re.match(r"(\d+)x(\d+)([-+]\d+)([-+]\d+)", s)
		if not m: raise ValueError("failed to parse geometry string")
		a = map(int, m.groups())
		return (a[2],a[3],a[0], a[1])
	
	def set_size_limits(self, xmin=None, xmax=None, ymin=None, ymax=None):
		"""Set min and max size of the window, in pixels. By default, size is not limited."""
		self._basecontrol.maxsize(xmax,ymax)
		self._basecontrol.minsize(xmin,ymin)
		
	def set_resizeable(self, bxresizeable, byresizeable):
		"""Set whether the dimensions of the window can be changed."""
		if bxresizeable: bxresizeable=1
		else: bxresizeable=0
		if byresizeable: byresizeable=1
		else: byresizeable=0
		self._basecontrol.resizable(bxresizeable,byresizeable)
	
	#todo: clear up distinction between these
	def activate(self):
		self._basecontrol.lift()
	def deactivate(self):
		self._basecontrol.lower()
	
	def hide(self):
		"""Hides the window."""
		self._basecontrol.withdraw()
	def show(self):
		"""Shows the window."""
		self._basecontrol.deiconify()
	def maximize(self):
		toplevel = self._basecontrol.winfo_toplevel()
		try: # On MS Windows one can set the "zoomed" state.
			toplevel.wm_state('zoomed')
		except:
			w = root.winfo_screenwidth()
			h = root.winfo_screenheight() - 60
			geom_string = "%dx%d+0+0" % (w,h)
			toplevel.wm_geometry(geom_string)
		
	def minimize(self):
		"""Minimizes (iconify) the window."""
		self._basecontrol.iconify()
	
	
	def set_icon(self,icon, mask=-1):
		"""Set icon for the window. In Windows, you can provide a filename such as 'test.ico'. Also, you can provide a mask for transparency."""
		self._basecontrol.iconbitmap(icon)
		if mask!=-1: self._basecontrol.iconmask(mask)
			
	
	def property_util_get(self):
		if self._utilClass==None: self._utilClass = rwindowutil.RWindowUtil()
		return self._utilClass
		
	def property_util_set(self, value):
		raise rcontrolutil.PyrindPropertyException('Cannot set this property.')
	
	def property_transparency_get(self):
		return self._basecontrol.attributes('-alpha')
	def property_transparency_set(self, value):
		v = float(value)
		self._basecontrol.attributes('-alpha', v)
			
	
	def property_enabled_get(self):
		return not self._basecontrol.attributes('-disabled')
	def property_enabled_set(self, value):
		if value: v=0
		else: v=1
		self._basecontrol.attributes('-disabled', v)
	
	def property_topmost_get(self):
		return not not self._basecontrol.attributes('-topmost')
	def property_topmost_set(self, value):
		if value: v=1
		else: v=0
		self._basecontrol.attributes('-topmost', v)
	
	def property_title_get(self):
		return self._basecontrol.title()
	def property_title_set(self, value):
		self._basecontrol.title(value)
			
		
	#todo: add event binding:
	#~ code for binding 'onclose':
	#~ if bSet:
		#~ if value==None:
			#~ self._basecontrol.protocol('WM_DELETE_WINDOW', None)
		#~ else:
			#~ # if the callback returns False, keep the window open. Otherwise, close the window.
			#~ def fnonclose(self=self, fn=value):
				#~ res = fn()
				#~ if res!=False: self.destroy()
			#~ self._basecontrol.protocol('WM_DELETE_WINDOW', fnonclose)
		#~ self._onclose = value
	#~ else: return self._oncloses
	#~ code for binding 'onfocus':
	#~ if bSet:
		#~ self._basecontrol.protocol('WM_TAKE_FOCUS', value)
		#~ self._onfocus = value
	#~ else: return self._onfocus
		
	
	_properties = {

		'util': (property_util_get,property_util_set),
		'transparency': (property_transparency_get,property_transparency_set),
		'topmost': (property_topmost_get,property_topmost_set),
		'enabled': (property_enabled_get,property_enabled_set),
		'title': (property_title_get,property_title_set),

		'bgcolor': ('background',rcontrolutil.mapColor),
		'borderwidth': 'borderwidth',
		'cursor': 'cursor',
		'width': 'width',
		'height': 'height',
		'borderstyle': ('relief', rcontrolutil.mapBorderstyle),
			
		}


	
	
	
	